using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Perovich.GameObjects.Interfaces;

namespace Perovich.GameObjects.ThreeDimensional
{

    /// <summary>
    /// The <see cref="AttachableCamera"/> is a <see cref="Camera"/> that can be attached to another <see cref="DrawableObject3D"/>.
    /// </summary>
    /// <remarks>The <see cref="AttachableCamera"/> class is useful for creating a camera that follows a player character.</remarks>
    public class AttachableCamera : Camera
    {
        bool _attached;
        ICanBeAttachedTo _attachTarget;
        Vector3 _attachHeadOffset;
        Vector3 _attachTargetOffset;

        /// <summary>
        /// The offset from the <see cref="AttachableCamera.AttachTarget"/> to place the <see cref="Camera.Target"/> property. 
        /// </summary>
        /// <value>The attach target offset, relative to the target's rotation.</value>
        public Vector3 AttachTargetOffset
        {
            get { return _attachTargetOffset; }
            set { _attachTargetOffset = value; }
        }

        /// <summary>
        /// The offset from the <see cref="AttachableCamera.AttachTarget"/> to place the <see cref="AttachableCamera"/> object. 
        /// </summary>
        /// <value>The attach target offset, relative to the target's rotation.</value>
        public Vector3 AttachHeadOffset
        {
            get { return _attachHeadOffset; }
            set { _attachHeadOffset = value; }
        }

        /// <summary>
        /// Gets or sets the <see cref="DrawableObject3D"/> this <see cref="AttachableCamera"/> is attached to.
        /// </summary>
        public ICanBeAttachedTo AttachTarget
        {
            get { return _attachTarget; }
            set { _attachTarget = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the <see cref="AttachableCamera"/> follows its target.
        /// </summary>
        /// <value>
        /// true to follow the target; false otherwise.
        /// </value>
        public bool Attached
        {
            get { return _attached; }
            set { _attached = value; }
        }

        /// <summary>
        /// Constructs a new instance of the <see cref="AttachableCamera"/> class.
        /// </summary>
        /// <param name="game"><see cref="Perovich.GameObjects.Interfaces.ICanHoldComponents"/> instance that this <see cref="AttachableCamera"/> will be constructed for.</param>
        public AttachableCamera(Interfaces.ICanHoldComponents game)
            : base(game)
        {
            UpdateOrder = int.MaxValue;
        }

        /// <summary>
        /// Attaches the <see cref="AttachableCamera"/> to the given <see cref="ICanBeAttachedTo"/>.
        /// </summary>
        /// <param name="obj">The <see cref="ICanBeAttachedTo"/> to attach to.</param>
        /// <param name="headOffset">The head offset.</param>
        /// <param name="targetOffset">The target offset.</param>
        public void AttachTo(ICanBeAttachedTo obj, Vector3 headOffset, Vector3 targetOffset)
        {
            if (obj == null)
                throw new ArgumentNullException("obj");
            _attached = true;
            _attachTarget = obj;
            _attachHeadOffset = headOffset;
            _attachTargetOffset = targetOffset;
            obj.PositionChanged += new EventHandler(obj_PositionChanged);
            obj_PositionChanged(this, null);
        }

        void obj_PositionChanged(object sender, EventArgs e)
        {
            Matrix rotationMatrix = Matrix.CreateRotationY(_attachTarget.RotationY) * Matrix.CreateRotationX(_attachTarget.RotationX);

            Vector3 transformedheadOffset =
                Vector3.Transform(_attachHeadOffset, rotationMatrix);
            Vector3 transformedReference =
                Vector3.Transform(_attachTargetOffset, rotationMatrix);

            Position = _attachTarget.Position + transformedheadOffset;
            Target = _attachTarget.Position + transformedReference;
        }


        /// <summary>
        /// Allows the <see cref="AttachableCamera"/> to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of the game's timing values.</param>
        public override void Update(GameTime gameTime)
        {
            if (_attached)
            {
                Matrix rotationMatrix = Matrix.CreateRotationY(_attachTarget.RotationY) * Matrix.CreateRotationX(_attachTarget.RotationX);

                Vector3 transformedheadOffset =
                    Vector3.Transform(_attachHeadOffset, rotationMatrix);
                Vector3 transformedReference =
                    Vector3.Transform(_attachTargetOffset, rotationMatrix);

                Position = _attachTarget.Position + transformedheadOffset;
                Target = _attachTarget.Position + transformedReference;
            }
            base.Update(gameTime);
        }

    }
}
